package com.growthgate.bootcommon;

import com.google.common.collect.Maps;
import com.growthgate.bootcommon.common.Constants;
import com.growthgate.bootcommon.common.base.BaseEntity;
import com.growthgate.bootcommon.util.ArrayUtils;
import com.growthgate.bootcommon.util.ClassUtils;
import com.growthgate.bootcommon.util.DateUtils;
import com.growthgate.bootcommon.util.StringUtils;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.RootDoc;
import com.sun.tools.javadoc.Main;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.annotation.CreatedDate;

import javax.persistence.Entity;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

/**
 * 代码生成器
 *
 * @author GrowthGate
 * @date 2020/2/27
 */
@Slf4j
public class CodeGenerator {

    public static final String BASE_PACKAGE = CodeGenerator.class.getPackage().getName();
    public static final String AUTHOR = "GrowthGate";
    public static final String DATE = DateUtils.toString(new Date(), DateUtils.SPECIAL_DATE);

    private static RootDoc rootDoc;

    public static void main(String[] args) throws IOException {
        // 各类所在的包
        String entityPackageName = BASE_PACKAGE + ".entity";
        String repositoryPackageName = BASE_PACKAGE + ".repository";
        String servicePackageName = BASE_PACKAGE + ".service";
        String serviceImplPackageName = BASE_PACKAGE + ".service.impl";
        String controllerPackageName = BASE_PACKAGE + ".controller";

        Configuration configuration = new Configuration(Configuration.VERSION_2_3_29);
        configuration.setClassForTemplateLoading(CodeGenerator.class, "/template");
        // 模板
        Template repositoryTemplate = configuration.getTemplate("repository.ftl");
        Template serviceTemplate = configuration.getTemplate("service.ftl");
        Template serviceImplTemplate = configuration.getTemplate("service_impl.ftl");
        Template controllerTemplate = configuration.getTemplate("controller.ftl");

        // 获取所有实体类
        List<Class<?>> entities = new ArrayList<>(ClassUtils.getClasses(entityPackageName, false));
        if (entities.isEmpty()) {
            return;
        }
        List<String> entityDescriptions = getEntityDescriptions(entities);

        for (int i = 0; i < entities.size(); i++) {
            Class<?> entity = entities.get(i);
            String entityDescription = entityDescriptions.get(i);
            if (BaseEntity.class.isAssignableFrom(entity)) {
                String entityName = entity.getSimpleName();
                // 生成各类代码
                generateFile(repositoryPackageName, entityName, entityDescription, "Repository", repositoryTemplate);
                generateFile(servicePackageName, entityName, entityDescription, "Service", serviceTemplate);
                generateFile(serviceImplPackageName, entityName, entityDescription, "ServiceImpl", serviceImplTemplate);
                generateFile(controllerPackageName, entityName, entityDescription, "Controller", controllerTemplate);
            }
        }
    }

    /**
     * 获取所有实体类的描述
     *
     * @param entities 实体类列表
     * @return 描述列表
     */
    private static List<String> getEntityDescriptions(@NotNull List<Class<?>> entities) {
        // 获取当前项目以及引用包的classpath，如果只设为当前项目classpath的话会报找不到程序包的警告
        List<Class<?>> classes = Arrays.asList(CodeGenerator.class, ApiModel.class, Data.class, CreatedDate.class, Entity.class);
        List<String> classPaths = classes.stream().map(CodeGenerator::getClasspath).collect(Collectors.toList());
        String classesPath = StringUtils.join(classPaths, File.pathSeparatorChar);
        // 需要解析类的路径
        String[] entityPaths = entities.stream().map(c -> getPackagePath(c.getPackage().getName()) + c.getSimpleName() + ".java").toArray(String[]::new);
        String[] options = {"-doclet", Doclet.class.getName(), "-encoding", Constants.DEFAULT_CHARSET, "-classpath", classesPath, "-quiet"};
        options = ArrayUtils.addAll(options, entityPaths);
        Main.execute(options);
        // 使用javadoc解析出注释
        ClassDoc[] classDocs = rootDoc.classes();
        return Arrays.stream(classDocs).map(Doc::commentText).collect(Collectors.toList());
    }

    /**
     * 获取类所在的classpath
     * 如果是本地项目的类，获取到的是/target/classes所在的路径
     * 如果是jar包里的类，获取到的是jar
     *
     * @param clazz 类
     * @return classpath
     */
    @NotNull
    private static String getClasspath(@NotNull Class<?> clazz) {
        // substring(1) 去掉最前面的斜杠
        return clazz.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1);
    }

    private static String getPackagePath(String packageName) {
        String projectPath = System.getProperty("user.dir");
        String packagePath = projectPath + ".src.main.java." + packageName + '.';
        packagePath = packagePath.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
        return packagePath;
    }

    private static void generateFile(String packageName, String entityName, String entityDescription, String alias, Template template) {
        // 获取文件路径
        String filePath = getPackagePath(packageName) + entityName + alias + ".java";
        File file = new File(filePath);
        if (!file.exists()) {
            Map<String, Object> map = Maps.newHashMapWithExpectedSize(4);
            map.put("basePackage", BASE_PACKAGE);
            map.put("author", AUTHOR);
            map.put("date", DATE);
            map.put("name", entityName);
            map.put("description", entityDescription);

            try (OutputStream os = new FileOutputStream(file);
                    OutputStreamWriter osw = new OutputStreamWriter(os, Constants.DEFAULT_CHARSET);
                    BufferedWriter bw = new BufferedWriter(osw)) {
                template.process(map, bw);
                log.info("generate {} success!", filePath);
            } catch (IOException | TemplateException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 接收RootDoc对象保存起来供后续使用
     */
    public static class Doclet {

        @SuppressWarnings("unused")
        public static boolean start(RootDoc root) {
            CodeGenerator.rootDoc = root;
            return true;
        }

    }

}